/******************************************************************************

    Copyright (C) 2019 ErisedMedia
    All Rights Reserved.

    This source code and any compilation or derivative thereof is the
    proprietary information of ErisedMedia and is
    confidential in nature.
    Under no circumstances is this software to be exposed to or placed under an
    Open Source License of any type without the expressed written permission of
    ErisedMedia.

******************************************************************************/

/******************************************************************************

    File:   Jpeg.cc
    Brief:  Jpeg实现， jpeg编解码接口封装.

******************************************************************************/

/******************************************************************************

    Rev     Date        Author      Comments
--------------------------------------------------------------------------------
    001     20190314    RunyuanYe        Original

--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.

*******************************************************************************/

/******************************************************************************
*
*   INCLUDE FILES
*
******************************************************************************/

#include "Jpeg.h"
#include "Tools.h"
// #define USE_FFMPEG
#if defined USE_FFMPEG
#include "decoder.h"
#endif

/******************************************************************************
*
*    LOCAL MACROS
*
******************************************************************************/

#define USE_TURBOJPEG   1


/******************************************************************************
*
*    LOCAL TYPEDEFS
*
******************************************************************************/


/******************************************************************************
*
*    STATIC DATA
*
******************************************************************************/


/******************************************************************************
*
*    EXPORTED DATA
*
******************************************************************************/


/******************************************************************************
*
*    EXPORTED FUNCTION
*
******************************************************************************/


/******************************************************************************
*
*    STATIC FUNCTION PROTOTYPES
*
******************************************************************************/


/******************************************************************************
*
*    FUNCTION IMPLEMENTATION
*
******************************************************************************/

/*
    创建JPEG解码器函数

    成功返回解码器句柄, 失败返回nullptr
*/
void* CreateDecodeJPEG()
{
    #if defined USE_FFMPEG
    return DecoderCreate(1);
    #else
    #if USE_TURBOJPEG
    return tjInitDecompress();
    #else
    return (void*)1;
    #endif
    #endif
}

/*
    销毁JPEG解码器函数

    handle： 解码器句柄
*/
void DestroyDecodeJPEG(void* handle)
{
    if(nullptr == handle) {
        return;
    }

    #if defined USE_FFMPEG
    DecoderDestroy(&handle);
    #else
    #if USE_TURBOJPEG
    tjDestroy(handle);
    #endif
    #endif
}

#if defined USE_OPENCV
/*
    JPEG解码函数

    handle： 解码器句柄
    jpgBuf： jpeg图片编码数据缓存
    size： jpeg图片编码数据大小
    image： 输出解码后的图片

    成功返回0, 失败返回非0
*/
int DecodeJPEG(void* handle, const void* jpgBuf, int size, Mat& image)
{
    int ret = -1;
    #if defined USE_FFMPEG
    if(nullptr==handle)
    {
        return ret;
    }
    int width = 0;
    int height = 0;
    void* data[3];
    int stride[3];

    START_PROFILE_TIME(pt0, "FFmpeg Decode: ");

    if(DecoderDecode(handle, (void*)jpgBuf, size, data, stride, &width, &height) < 0)
    {
        END_PROFILE_TIME(pt0);
        return ret;
    }
    END_PROFILE_TIME(pt0);

    image.release();
    image.create(height, width, CV_8UC3);

    START_PROFILE_TIME(pt1, "libyuv::I420ToRGB24: ");

    libyuv::I420ToRGB24((const unsigned char*)data[0], stride[0], (const unsigned char*)data[1], stride[1],
                    (const unsigned char*)data[2], stride[2], (unsigned char*)image.data, image.step[0],
                    width, height);
    END_PROFILE_TIME(pt1);
    #else
    #if USE_TURBOJPEG
    tjhandle tjInstance = (tjhandle)handle;
    if(nullptr==tjInstance)
    {
        return ret;
    }
    int width = 0;
    int height = 0;
    int inSubsamp;
    int inColorspace;
    if (tjDecompressHeader3(tjInstance, (unsigned char*)jpgBuf, size, &width, &height,
                            &inSubsamp, &inColorspace) < 0)
    {
        DBG_PRINT_EX(DBG_LEVEL_ERROR1, "tjDecompressHeader3 Error!\n");
        return ret;
    }
    image.release();
    image.create(height, width, CV_8UC3);
    // DBG_PRINT_EX(DBG_LEVEL_1, "width %d, height %d, inSubsamp %d, inColorspace %d\n", width, height, inSubsamp, inColorspace);

    //int flags = 0; 
    //TJFLAG_FASTUPSAMPLE //Using fast upsampling code
    //TJFLAG_FASTDCT //Using fastest DCT/IDCT algorithm
    //TJFLAG_ACCURATEDCT //Using most accurate DCT/IDCT algorithm
    /*if((inColorspace == TJPF_BGR)||(inColorspace == TJPF_RGB))
    {
        if (tjDecompress2(tjInstance, (unsigned char*)jpgBuf, size, (unsigned char*)image.data, width, image.step[0], height,
                        TJPF_BGR, flags) < 0)
        {
            return ret;
        }
    }
    else*/
    {
        int HStride = ((height+0x1)&~0x1);
        int YStride = ((width+0x7)&~0x7);
        int UStride = YStride/2;
        int VStride = YStride/2;
        unsigned char* pYBuf = new unsigned char[YStride*HStride*2];
        unsigned char* pUBuf = pYBuf + YStride*HStride;
        unsigned char* pVBuf = pUBuf + UStride*HStride;

        if(!pYBuf)
        {
            return ret;
        }
        // START_PROFILE_TIME(pt0, "tjDecompressToYUV: ");
        unsigned char* dstPlanes[3] = {pYBuf, pUBuf, pVBuf};
        int strides[3] = {YStride, UStride, VStride};
        if(tjDecompressToYUVPlanes(tjInstance, (unsigned char*)jpgBuf, size, dstPlanes, width, strides, height, 1==inSubsamp?TJ_422:TJ_420) < 0)
        {
            delete[] pYBuf;
            DBG_PRINT_EX(DBG_LEVEL_ERROR1, "tjDecompressToYUVPlanes Error: w %d, h %d, ys %d, us %d, vs %d, hs %d, inSubsamp %d\n", width, height, YStride, UStride, UStride, HStride, inSubsamp);
            return ret;
        }
        // END_PROFILE_TIME(pt0);
        // START_PROFILE_TIME(pt1, "libyuv::I420ToRGB24: ");
        if(1==inSubsamp)
        {
            UStride <<= 1;
            VStride <<= 1;
        }
        //libyuv::I420ToRAW((const unsigned char*)pYBuf, YStride, (const unsigned char*)pUBuf, UStride,
        libyuv::I420ToRGB24((const unsigned char*)pYBuf, YStride, (const unsigned char*)pUBuf, UStride,
                        (const unsigned char*)pVBuf, VStride, (unsigned char*)image.data, image.step[0],
                        width, height);
        // END_PROFILE_TIME(pt1);
        delete[] pYBuf;
    }

    #else
    vector<unsigned char> frame_data((unsigned char*)(jpgBuf), (unsigned char*)(jpgBuf)+size);
    image = imdecode(frame_data, CV_LOAD_IMAGE_COLOR);
    if(image.empty())
    {
        return ret;
    }
    #endif
    #endif
    ret = 0;
    return ret;
}
#endif

/*
    JPEG解码函数

    handle： 解码器句柄
    jpgBuf： jpeg图片编码数据缓存
    size： jpeg图片编码数据大小
    pimage： 输出解码后的图片缓存指针，使用完需要手工调用DecodeFree释放内存
    pwidth: 输出解码后的图片宽度
    pheight: 输出解码后的图片高度
    pstride: 输出解码后的图片跨度

    成功返回0, 失败返回非0
*/
int DecodeJPEGEx(void* handle, const void* jpgBuf, int size, void** pimage, int* pwidth, int* pheight, int* pstride)
{
    int ret = -1;
    #if defined USE_FFMPEG
    if((nullptr==handle) || (nullptr==pimage) || (nullptr==pwidth) || (nullptr==pheight) || (nullptr==pstride))
    {
        return ret;
    }
    int width = 0;
    int height = 0;
    void* data[3];
    int stride[3];

    START_PROFILE_TIME(pt0, "FFmpeg Decode: ");
    if(DecoderDecode(handle, (void*)jpgBuf, size, data, stride, &width, &height) < 0)
    {
        END_PROFILE_TIME(pt0);
        return ret;
    }
    END_PROFILE_TIME(pt0);

    void* image = malloc(((height+0x1)&~0x1)*((width+0x7)&~0x7)*3);
    if(nullptr==image)
    {
        return ret;
    }
    int imageStride = width*3;

    START_PROFILE_TIME(pt1, "libyuv::I420ToRGB24: ");

    libyuv::I420ToRGB24((const unsigned char*)data[0], stride[0], (const unsigned char*)data[1], stride[1],
                    (const unsigned char*)data[2], stride[2], (unsigned char*)image, imageStride,
                    width, height);
    END_PROFILE_TIME(pt1);

    *pimage = image;
    *pwidth = width;
    *pheight= height;
    *pstride= imageStride;
    #else
    #if USE_TURBOJPEG
    tjhandle tjInstance = (tjhandle)handle;
    if((nullptr==tjInstance) || (nullptr==pimage) || (nullptr==pwidth) || (nullptr==pheight) || (nullptr==pstride))
    {
        return ret;
    }
    int width = 0;
    int height = 0;
    int inSubsamp;
    int inColorspace;
    if (tjDecompressHeader3(tjInstance, (unsigned char*)jpgBuf, size, &width, &height,
                            &inSubsamp, &inColorspace) < 0)
    {
        return ret;
    }
    void* image = malloc(((height+0x1)&~0x1)*((width+0x7)&~0x7)*3);
    if(nullptr==image)
    {
        return ret;
    }
    int imageStride = width*3;
    // DBG_PRINT_EX(DBG_LEVEL_1, "width %d, height %d, inSubsamp %d, inColorspace %d\n", width, height, inSubsamp, inColorspace);

    //int flags = 0; 
    //TJFLAG_FASTUPSAMPLE //Using fast upsampling code
    //TJFLAG_FASTDCT //Using fastest DCT/IDCT algorithm
    //TJFLAG_ACCURATEDCT //Using most accurate DCT/IDCT algorithm
    /*if((inColorspace == TJPF_BGR)||(inColorspace == TJPF_RGB))
    {
        if (tjDecompress2(tjInstance, (unsigned char*)jpgBuf, size, (unsigned char*)image.data, width, image.step[0], height,
                        TJPF_BGR, flags) < 0)
        {
            free(image);
            return ret;
        }
    }
    else*/
    {
        int HStride = ((height+0x1)&~0x1);
        int YStride = ((width+0x7)&~0x7);
        int UStride = YStride/2;
        int VStride = YStride/2;
        unsigned char* pYBuf = new unsigned char[YStride*HStride*2];
        unsigned char* pUBuf = pYBuf + YStride*HStride;
        unsigned char* pVBuf = pUBuf + UStride*HStride;

        if(!pYBuf)
        {
            free(image);
            return ret;
        }
        // START_PROFILE_TIME(pt0, "tjDecompressToYUV: ");
        unsigned char* dstPlanes[3] = {pYBuf, pUBuf, pVBuf};
        int strides[3] = {YStride, UStride, VStride};
        if(tjDecompressToYUVPlanes(tjInstance, (unsigned char*)jpgBuf, size, dstPlanes, width, strides, height, 1==inSubsamp?TJ_422:TJ_420) < 0)
        {
            delete[] pYBuf;
            free(image);
            return ret;
        }
        // END_PROFILE_TIME(pt0);
        // START_PROFILE_TIME(pt1, "libyuv::I420ToRGB24: ");
        if(1==inSubsamp)
        {
            UStride <<= 1;
            VStride <<= 1;
        }
        //libyuv::I420ToRAW((const unsigned char*)pYBuf, YStride, (const unsigned char*)pUBuf, UStride,
        libyuv::I420ToRGB24((const unsigned char*)pYBuf, YStride, (const unsigned char*)pUBuf, UStride,
                        (const unsigned char*)pVBuf, VStride, (unsigned char*)image, imageStride,
                        width, height);
        // END_PROFILE_TIME(pt1);
        delete[] pYBuf;

        *pimage = image;
        *pwidth = width;
        *pheight= height;
        *pstride= imageStride;
    }
    #endif
    #endif
    ret = 0;
    return ret;
}

/*
    JPEG解码函数

    handle： 解码器句柄
    jpgBuf： jpeg图片编码数据缓存
    size： jpeg图片编码数据大小
    pimage： 输出解码后的图片缓存指针，使用完需要手工调用DecodeFree释放内存
    pwidth: 输出解码后的图片宽度
    pheight: 输出解码后的图片高度
    pstride: 输出解码后的图片跨度

    成功返回0, 失败返回非0
*/
int DecodeJPEGYUVEx(void* handle, const void* jpgBuf, int size, void** pimage, int* pwidth, int* pheight, int* pstride)
{
    int ret = -1;
    #if defined USE_FFMPEG
    return ret;
    #else
    #if USE_TURBOJPEG
    tjhandle tjInstance = (tjhandle)handle;
    if((nullptr==tjInstance) || (nullptr==pimage) || (nullptr==pwidth) || (nullptr==pheight) || (nullptr==pstride))
    {
        return ret;
    }
    int width = 0;
    int height = 0;
    int inSubsamp;
    int inColorspace;
    if (tjDecompressHeader3(tjInstance, (unsigned char*)jpgBuf, size, &width, &height,
                            &inSubsamp, &inColorspace) < 0)
    {
        return ret;
    }

    // DBG_PRINT_EX(DBG_LEVEL_1, "width %d, height %d, inSubsamp %d, inColorspace %d\n", width, height, inSubsamp, inColorspace);

    //int flags = 0; 
    //TJFLAG_FASTUPSAMPLE //Using fast upsampling code
    //TJFLAG_FASTDCT //Using fastest DCT/IDCT algorithm
    //TJFLAG_ACCURATEDCT //Using most accurate DCT/IDCT algorithm
    /*if((inColorspace == TJPF_BGR)||(inColorspace == TJPF_RGB))
    {
        if (tjDecompress2(tjInstance, (unsigned char*)jpgBuf, size, (unsigned char*)image.data, width, image.step[0], height,
                        TJPF_BGR, flags) < 0)
        {
            return ret;
        }
    }
    else*/
    {
        int HStride = ((height+0x1)&~0x1);
        // int YStride = ((width+0x7)&~0x7);
        int YStride = ((width+0x3)&~0x3);
        int UStride = YStride/2;
        int VStride = YStride/2;
        unsigned char* pYBuf = (unsigned char *)(malloc(YStride*HStride*2));
        unsigned char* pUBuf = pYBuf + YStride*HStride;
        unsigned char* pVBuf = pUBuf + UStride*HStride;

        if(!pYBuf)
        {
            return ret;
        }
        // START_PROFILE_TIME(pt0, "tjDecompressToYUV: ");
        unsigned char* dstPlanes[3] = {pYBuf, pUBuf, pVBuf};
        int strides[3] = {YStride, UStride, VStride};
        if(tjDecompressToYUVPlanes(tjInstance, (unsigned char*)jpgBuf, size, dstPlanes, width, strides, height, 1==inSubsamp?TJ_422:TJ_420) < 0)
        {
            free(pYBuf);
            return ret;
        }
        // END_PROFILE_TIME(pt0);

        *pimage = pYBuf;
        *pwidth = width;
        *pheight= height;
        *pstride= YStride;
    }
    #endif
    ret = 0;
    return ret;
    #endif
}

/*
    图像内存释放函数

    与DecodeJPEGEx函数配套使用

    image： 图像内存

*/
void DecodeFree(void* image)
{
    if(image)
    {
        free(image);
    }
}

/*
    创建JPEG编码器函数

    成功返回编码器句柄, 失败返回nullptr
*/
void* CreateEncodeJPEG()
{
    #if USE_TURBOJPEG
    return tjInitCompress();
    #else
    return (void*)1;
    #endif
}

/*
    销毁JPEG编码器函数

    handle： 编码器句柄
*/
void DestroyEncodeJPEG(void* handle)
{
    if(nullptr == handle) {
        return;
    }

    #if USE_TURBOJPEG
    if(nullptr!=handle)
    {
        tjDestroy(handle);
    }
    #endif
}

/*
    JPEG编码函数

    handle： 编码器句柄
    imageData： 输入需要编码的图片内存地址
    width：输入的图片宽度
    height：输入的图片高度
    stride：输入的图片一行内存跨度
    jpgBuf： 输出jpeg图片编码后的数据缓存

    成功返回0, 失败返回非0
*/
int EncodeJPEGEx(void* handle, void* imageData, int width, int height, int stride, vector<unsigned char>& jpgBuf)
{
    int ret = -1;
    #if USE_TURBOJPEG
    tjhandle tjInstance = (tjhandle)handle;
    if(nullptr==tjInstance)
    {
        return ret;
    }
    
    int outSubsamp = TJSAMP_420;
    int outQual = 95;
    int flags = TJFLAG_FASTDCT; //0;//
    //TJFLAG_FASTUPSAMPLE //Using fast upsampling code
    //TJFLAG_FASTDCT //Using fastest DCT/IDCT algorithm
    //TJFLAG_ACCURATEDCT //Using most accurate DCT/IDCT algorithm
    unsigned char *jpegBuf = NULL;  /* Dynamically allocate the JPEG buffer */
    unsigned long jpegSize = 0;

    int HStride = ((height+0x1)&~0x1);
    int YStride = ((width+0x7)&~0x7);
    int UStride = YStride/2;
    int VStride = YStride/2;
    unsigned char* pYBuf = new unsigned char[YStride*HStride*2];
    unsigned char* pUBuf = pYBuf + YStride*HStride;
    unsigned char* pVBuf = pUBuf + UStride*HStride;

    if(!pYBuf)
    {
        return ret;
    }
    START_PROFILE_TIME(pt0, "libyuv::RGB24ToI420: ");
    //RAWToI420
    libyuv::RGB24ToI420((unsigned char*)imageData, stride, (unsigned char*)pYBuf, YStride, 
                (unsigned char*)pUBuf, UStride, (unsigned char*)pVBuf, VStride, width, height);
    END_PROFILE_TIME(pt0);
    START_PROFILE_TIME(pt1, "tjCompressFromYUV: ");
    
    const unsigned char* srcPlanes[3] = {pYBuf, pUBuf, pVBuf};
    int strides[3] = {YStride, UStride, VStride};

    if (tjCompressFromYUVPlanes(tjInstance, srcPlanes, width, strides, height, outSubsamp, &jpegBuf, &jpegSize, outQual, flags) < 0)
    {
        DBG_PRINT_EX(DBG_LEVEL_ERROR1, "tjCompressFromYUV Error!\n");
        delete[] pYBuf;
        END_PROFILE_TIME(pt1);
        return ret;
    }
    END_PROFILE_TIME(pt1);
    delete[] pYBuf;
    jpgBuf.assign(jpegBuf, jpegBuf+jpegSize);
    memcpy(&jpgBuf[0], jpegBuf, jpegSize);

    tjFree(jpegBuf);
    #endif
    ret = 0;
    return ret;
}

/*
    JPEG编码函数

    handle： 编码器句柄
    imageData： 输入需要编码的图片内存地址
    width：输入的图片宽度
    height：输入的图片高度
    stride：输入的图片一行内存跨度
    ppjpgBuf： 输出jpeg图片编码后的数据缓存地址
    pjpgSize: 输出jpeg图片编码后的数据缓存大小

    成功返回0, 失败返回非0
*/
int EncodeJPEGEx2(void* handle, void* imageData, int width, int height, int stride, void** ppjpgBuf, int* pjpgSize)
{
    int ret = -1;
    #if USE_TURBOJPEG
    tjhandle tjInstance = (tjhandle)handle;
    if(nullptr==tjInstance)
    {
        return ret;
    }
    
    int outSubsamp = TJSAMP_420;
    int outQual = 95;
    int flags = TJFLAG_FASTDCT; //0;//
    //TJFLAG_FASTUPSAMPLE //Using fast upsampling code
    //TJFLAG_FASTDCT //Using fastest DCT/IDCT algorithm
    //TJFLAG_ACCURATEDCT //Using most accurate DCT/IDCT algorithm
    unsigned char *jpegBuf = NULL;  /* Dynamically allocate the JPEG buffer */
    unsigned long jpegSize = 0;

    int HStride = ((height+0x1)&~0x1);
    int YStride = ((width+0x7)&~0x7);
    int UStride = YStride/2;
    int VStride = YStride/2;
    unsigned char* pYBuf = new unsigned char[YStride*HStride*2];
    unsigned char* pUBuf = pYBuf + YStride*HStride;
    unsigned char* pVBuf = pUBuf + UStride*HStride;

    if(!pYBuf)
    {
        return ret;
    }
    START_PROFILE_TIME(pt0, "libyuv::RGB24ToI420: ");
    //RAWToI420
    libyuv::RGB24ToI420((unsigned char*)imageData, stride, (unsigned char*)pYBuf, YStride, 
                (unsigned char*)pUBuf, UStride, (unsigned char*)pVBuf, VStride, width, height);
    END_PROFILE_TIME(pt0);
    START_PROFILE_TIME(pt1, "tjCompressFromYUV: ");
    
    const unsigned char* srcPlanes[3] = {pYBuf, pUBuf, pVBuf};
    int strides[3] = {YStride, UStride, VStride};

    if (tjCompressFromYUVPlanes(tjInstance, srcPlanes, width, strides, height, outSubsamp, &jpegBuf, &jpegSize, outQual, flags) < 0)
    {
        DBG_PRINT_EX(DBG_LEVEL_ERROR1, "tjCompressFromYUV Error!\n");
        delete[] pYBuf;
        END_PROFILE_TIME(pt1);
        return ret;
    }
    END_PROFILE_TIME(pt1);
    delete[] pYBuf;
    if(ppjpgBuf)
    {
        *ppjpgBuf = jpegBuf;
    }
    if(pjpgSize)
    {
        *pjpgSize = (int)(jpegSize);
    }
    #endif
    ret = 0;
    return ret;
}

/*
    图像内存释放函数

    与EncodeJPEGEx2函数配套使用

    image： 图像内存

*/
void EncodeFree(void* image)
{
    if(image)
    {
        tjFree((unsigned char *)image);
    }
}

#if defined USE_OPENCV
/*
    JPEG编码函数

    handle： 编码器句柄
    image： 输入需要编码的图片
    jpgBuf： 输出jpeg图片编码后的数据缓存

    成功返回0, 失败返回非0
*/
int EncodeJPEG(void* handle, Mat& image, vector<unsigned char>& jpgBuf)
{
    int ret = -1;
    #if USE_TURBOJPEG
    return EncodeJPEGEx(handle, image.data, image.cols, image.rows, image.step[0], jpgBuf);
    #else
    if(!cv::imencode(".jpg", image, jpgBuf))
    {
        DBG_PRINT_EX(DBG_LEVEL_ERROR1, "imencode Error!\n");
        return ret;
    }
    #endif
    ret = 0;
    return ret;
}
#endif
